package net.hangar5.xmlrpc;

/* XmlParser.java

The contents of this file are subject to the Mozilla Public
License Version 1.1 (the "License"); you may not use this file
except in compliance with the License. You may obtain a copy of
the License at http://www.mozilla.org/MPL/

Software distributed under the License is distributed on an "AS
IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
implied. See the License for the specific language governing
rights and limitations under the License.

The Original Code is "Hangar5 XMLRPC Library".

The Initial Developer of the Original Code is James D. Rudnicki.
Portions created by James D. Rudnicki are
Copyright (C) 2001.  All Rights Reserved.

Contributor(s):
*/
import java.util.*;

/** Dissects XML-RPC call data into method name and parameters.
 */
public class XmlParser {

  static protected final String tagValueO = "<value>";
  static protected final int sizeValueO = 7;
  static protected final String tagValueC = "</value>";
  static protected final int sizeValueC = 8;

  static protected final String tagStNameO = "<name>";
  static protected final int sizeStNameO = 6;

  protected String strSearch;

  protected int nIxProcThru;



public XmlParser(String s) {
	strSearch = s;
	return;
}
public XmlParser(StringBuffer sb) {
	strSearch = sb.toString();
	return;
}
public static boolean matched( String strScan, String strE, int j1, int j2 ) {
	String strOpen = "<"+ strE +">";
		String strClose = "</"+ strE +">";
	int nO = 0;
	int nC = 0;
	int j, k;



	j = j1;
	while( j < j2 ) {
	  k = strScan.indexOf( strOpen, j );
	  if( k < 0 ) {
		j = j2;
	  }
	  else if( k <= j2 ) {
		nO++;
		j = k + strOpen.length();
	  }
	  else {
		j = j2;
	  }
	}
	j = j1;
	while( j < j2 ) {
	  k = strScan.indexOf( strClose, j );
	  if( k < 0 ) {
		j = j2;
	  }
	  else if( k <= j2 ) {
		nC++;
		j = k + strClose.length();
	  }
	  else {
		j = j2;
	  }
	}

  return (nO==nC);
}
/** return object of type
   */
protected Vector parseArray(int ixFocus1, int ixFocus2) throws RpcException {
	Vector oV = new Vector();
	int j1;
	do {
		j1 = strSearch.indexOf(tagValueO, ixFocus1);
		if ((j1 >= 0) & (j1 < ixFocus2)) {
			Object o = parseValue(j1 + sizeValueO, ixFocus2);
			if (null != o) {
				oV.addElement(o);
			}
		}
		ixFocus1 = nIxProcThru;
	}
	while ((j1 >= 0) & (j1 < ixFocus2));
	nIxProcThru = ixFocus2;
	return oV;
}
/** return object of type
   */
protected byte[] parseBase64(int ixFocus1, int ixFocus2) throws RpcException {
  byte[] baRet = null;
	char[] chTemp = new char[ixFocus2 - ixFocus1 + 1];
	strSearch.getChars(ixFocus1, ixFocus2, chTemp, 0);
  baRet = helma.xmlrpc.Base64.decode( chTemp );
	nIxProcThru = ixFocus2;
	return baRet;
}
/** return object of type
   */
protected Boolean parseBoolean(int ixFocus1, int ixFocus2) throws RpcException {
	Boolean oB;
	/* coerce integer 0 = FALSE, not 0 = TRUE */
	try {
		Integer oI;
		oI = Integer.decode(strSearch.substring(ixFocus1, ixFocus2));
		if (oI.intValue() == 0) {
			oB = new Boolean(false);
		}
		else {
			oB = new Boolean(true);
		}
		nIxProcThru = ixFocus2;
	}
	catch (NumberFormatException x) {
		throw new RpcException(RpcException.BADPARAMVALUE, strSearch.substring(ixFocus1, ixFocus2));
	}
	return oB;
}
/** return object of type
   */
protected Double parseDouble(int ixFocus1, int ixFocus2) throws RpcException {
	Double oD;
	try {
		oD = Double.valueOf(strSearch.substring(ixFocus1, ixFocus2));
		nIxProcThru = ixFocus2;
	}
	catch (NumberFormatException x) {
		throw new RpcException(RpcException.BADPARAMVALUE, strSearch.substring(ixFocus1, ixFocus2));
	}
	return oD;
}
/** return object of type
   */
protected Integer parseInt(int ixFocus1, int ixFocus2) throws RpcException {
	Integer oI;
	try {
		oI = Integer.decode(strSearch.substring(ixFocus1, ixFocus2));
		nIxProcThru = ixFocus2;
	}
	catch (NumberFormatException x) {
		throw new RpcException(RpcException.BADPARAMVALUE, strSearch.substring(ixFocus1, ixFocus2));
	}
	return oI;
}
  protected String parseName( int j1 ) throws RpcException {
	String strRet = null;
	int j2, j3, j4;
	String strType;

	/* on entry, j1 is the index of first character after <name> tag */

	/* pull type tag out of string */
	j2 = strSearch.indexOf( "<", j1 );
	if( j2 > 0  ) {
	  j3 = strSearch.indexOf( ">", j2 );
	  strType = strSearch.substring( j2+1, j3 );

	  if( 0 == strType.compareTo( "/name" ) ) {
		strRet = strSearch.substring( j1, j2 );

		nIxProcThru = j3;
	  }
	  else {
		throw new RpcException( RpcException.GENERIC, "missing name" );
	  }
	}
	else {
	  throw new RpcException( RpcException.GENERIC, "missing name" );
	}

	return strRet;
  }
  /**
   *
   */
  protected Date parseDate( int ixFocus1, int ixFocus2) throws RpcException {
    Date oD = null;
    int y, m, d, hh, mm, ss;
    int k = ixFocus1;
    String s;
    try {
      s = strSearch.substring(k,k+4);
      y = Integer.parseInt( s );
      k += 4;
      if( strSearch.charAt(k) == '-' ) {
        k++;
      }
      s = strSearch.substring(k,k+2);
      m = Integer.parseInt( s ) - 1;
      k+= 2;
      if( strSearch.charAt(k) == '-' ) {
        k++;
      }
      s = strSearch.substring(k,k+2);
      d = Integer.parseInt( s );
      k += 3;

      s = strSearch.substring(k,k+2);
      hh = Integer.parseInt( s );
      k += 3;
      s = strSearch.substring(k,k+2);
      mm = Integer.parseInt( s );
      k += 3;
      s = strSearch.substring(k,k+2);
      ss = Integer.parseInt( s );
      k += 3;

      GregorianCalendar gc = new GregorianCalendar( y, m, d, hh, mm, ss );
      oD = gc.getTime();
    }
    catch( NumberFormatException x ) {
    }
    return oD;
  }
/** return object of type
   */
protected String parseString(int ixFocus1, int ixFocus2) throws RpcException {
	String oS;
	oS = strSearch.substring(ixFocus1, ixFocus2);
	oS = unescapeString(oS);
	nIxProcThru = ixFocus2;
	return oS;
}
/** return object of type
   */
protected Hashtable parseStruct(int ixFocus1, int ixFocus2) throws RpcException {
	Hashtable oH = new Hashtable();
	int j;
	do {
		j = strSearch.indexOf(tagStNameO, ixFocus1);
		if((j >= 0) & (j < ixFocus2)) {
			String s;
			s = parseName(j + sizeStNameO);
			j = strSearch.indexOf(tagValueO, ixFocus1);
			Object o = null;
			if (j < ixFocus2) {
				o = parseValue(j + sizeValueO, ixFocus2);
			}
			if (null != o) {
				oH.put(s, o);

			}
		}
		ixFocus1 = nIxProcThru;
	} while((j >= 0) & (j <ixFocus2));

	nIxProcThru = ixFocus2;
	return oH;
}
/**
   * Return next &lt;value&gt; starting at index
   */
protected Object parseValue(int ixFocus1, int ixFocus2) throws RpcException {
	Object oRet = null;
	int i1, i2, j1, j2;
	String strType;

	/* on entry, ixFocus1 is the index of first character after <value> tag */


	/* pull type tag out of string */
	j1 = strSearch.indexOf("<", ixFocus1);
	if( j1 <= 0 ) {
		throw new RpcException(RpcException.GENERIC, RpcException.SGENERIC);
	}

	j2 = strSearch.indexOf(">", j1);
	strType = strSearch.substring(j1 + 1, j2);

	/* For whatever reason, no tag is allowed and implies 'string' (wacked)
	   In this case, the closing </value> tag will have been found instead
	   of an opening <type> tag */
	if (0 == strType.compareTo("/value")) {
		strType = "string";
		i1 = ixFocus1;
		i2 = j1;
	}
	else {
	String strOpen = "<"+ strType +">";
		String strClose = "</"+strType+">";
		/* This is what makes nested collections work
		The focus must be moved in from the ends
		For scalars, grab only the next element.  When inside an array, the
		ixFocus2 will point to the end of all the elements */
		if( strType.equals( "array" ) | strType.equals( "struct" ) ) {
	  int k1, k2;
	  k1 = j1;
	  k2 = strSearch.indexOf( strClose, k1 );
	  while( (k1 < ixFocus2) & (!matched( strSearch, strType, j1, k2 )) ) {
		k1 = k2 + 1;
		k2 = strSearch.indexOf( strClose, k1 );
	  }
  	  i1 = j2+1;
	  i2 = k2;
		}
		else {
  		i1 = j2+1;
			i2 = strSearch.indexOf( strClose, i1 );
		}
		if( i2 < 0 ) {
			throw new RpcException( RpcException.PARSE, strSearch.substring( ixFocus1, ixFocus2 ) );
		}
	}

	/* at this point
	i1 is index of first character after <type> tag
	i2 is index of last character of value */



	if( strType.equals( "i4" ) | strType.equals( "int" ) ) {
		oRet = (Object)parseInt(i1,i2);
	}
	else if( strType.equals( "boolean" ) ) {
		oRet = (Object)parseBoolean(i1,i2);
	}
	else if( strType.equals( "double" ) ) {
		oRet = (Object)parseDouble(i1,i2);
	}
	else if( strType.equals( "string" ) ) {
	  oRet = (Object)parseString(i1,i2);
	}
  else if( strType.equals( "dateTime.iso8601" ) ) {
    oRet = (Object)parseDate(i1,i2);
  }
	else if( strType.equals( "base64" ) ) {
	  oRet = (Object)parseBase64(i1,i2);
	}
	else if( strType.equals( "array" ) ) {
		oRet = (Object)parseArray(i1,i2);
	}
	else if( strType.equals( "struct" ) ) {
		oRet = (Object)parseStruct(i1,i2);
	}
	else {
	  throw new RpcException( RpcException.BADPARAMTYPE, strType +" "+ RpcException.SBADPARAMTYPE );
	}



	return oRet;
}
  protected String unescapeString( String strIn ) throws RpcException {
	StringBuffer sbBuild = new StringBuffer( strIn.length() );
	String strEscaped;
	int nIxThru, n1, n2;
	String strRet;

	n1 = strIn.indexOf( '&' );
	if( n1 >= 0 ) {
	  nIxThru = 0;
	  while( n1 >= 0 ) {
		/* substring takes starting at first index
		and stops one character before second index */
		sbBuild.append( strIn.substring( nIxThru, n1 ) );

		n2 = strIn.indexOf( ';', n1 );
		strEscaped = strIn.substring( n1, n2+1 );
		if( strEscaped.equals( "&amp;" ) ) {
		  sbBuild.append( '&' );

		}
		else if(  strEscaped.equals( "&lt;" ) ) {
		  sbBuild.append( '<' );

		}
		else if(  strEscaped.equals( "&gt;" ) ) {
		  sbBuild.append( '>' );

		}
		else {
		  throw new RpcException( RpcException.BADPARAMVALUE, strIn );
		}
		nIxThru = n2+1;
		n1 = strIn.indexOf( '&', nIxThru );
	  }
	  if( nIxThru < strIn.length() ) {
		sbBuild.append( strIn.substring( nIxThru ) );

	  }

	  return sbBuild.toString();
	}
	else {
	  return strIn;
	}
  }
} // end of class
